<?php
namespace app\service\aiAvatar;

use think\facade\Db;
use app\service\TopviewApiService;

class VideoAvatarService
{
    protected $apiService;

    public function __construct(TopviewApiService $apiService)
    {
        $this->apiService = $apiService;
    }

    /**
     * 创建数字人视频任务
     * @param int $userId 用户ID
     * @param array $data 任务数据
     * @return array
     * @throws \Exception
     */
    public function createVideoAvatarTask($userId, $data)
    {
        // 计算需要扣除的积分
        $points = $this->calculatePoints($data);

        Db::startTrans();
        try {
            // 查询用户信息并加排他锁
            $user = Db::name('users')
                ->where('id', $userId)
                ->lock(true)
                ->find();

            if (!$user) {
                throw new \Exception('用户不存在');
            }

            if ($user['points'] < $points) {
                throw new \Exception('积分不足，无法生成视频');
            }

            // 构造API参数
            $params = [
                'avatarSourceFrom' => $data['avatarSourceFrom'] ?? 1, // 默认使用公模列表
                'audioSourceFrom' => $data['audioSourceFrom'] ?? 1,   // 默认使用TTS
            ];

            // 根据数字人来源设置参数
            if ($params['avatarSourceFrom'] == 0) {
                if (empty($data['videoFileId'])) {
                    throw new \Exception('使用上传视频时，videoFileId不能为空');
                }
                $params['videoFileId'] = $data['videoFileId'];
            } else {
                if (empty($data['aiAvatarId'])) {
                    throw new \Exception('使用公模数字人时，aiAvatarId不能为空');
                }
                $params['aiAvatarId'] = $data['aiAvatarId'];
            }

            // 根据音频来源设置参数
            if ($params['audioSourceFrom'] == 0) {
                if (empty($data['audioFileId'])) {
                    throw new \Exception('使用上传音频时，audioFileId不能为空');
                }
                $params['audioFileId'] = $data['audioFileId'];
            } else {
                if (empty($data['ttsText'])) {
                    throw new \Exception('使用TTS时，ttsText不能为空');
                }
                $params['ttsText'] = $data['ttsText'];
                // 当使用公模数字人时，voiceoverId可选（不传则使用数字人默认音色）
                if ($params['avatarSourceFrom'] != 1) {
                    if (empty($data['voiceoverId'])) {
                        throw new \Exception('使用TTS时，非公模数字人必须指定voiceoverId');
                    }
                    $params['voiceoverId'] = $data['voiceoverId'];
                } else if (!empty($data['voiceoverId'])) {
                    $params['voiceoverId'] = $data['voiceoverId'];
                }
            }

            // 可选参数
            if (!empty($data['noticeUrl'])) {
                $params['noticeUrl'] = $data['noticeUrl'];
            }

            $params['modeType'] = 0;
            // 调用API提交任务
            $result = $this->apiService->submitVideoAvatarTask($params);
            
            // 计算新的余额
            $newBalance = $user['points'] - $points;
            
            // 扣除用户积分
            Db::name('users')
                ->where('id', $userId)
                ->update(['points' => $newBalance]);

            // 记录积分变动
            Db::name('points_log')->insert([
                'user_id' => $userId,
                'change' => -$points,
                'balance' => $newBalance,
                'type' => 'consume',
                'remark' => '数字人生成',
                'create_time' => time()
            ]);
            
            // 保存任务信息到数据库
            $taskData = [
                'task_id' => $result['taskId'],
                'user_id' => $userId,
                'avatar_source_from' => $params['avatarSourceFrom'],
                'video_file_id' => $params['videoFileId'] ?? '',
                'ai_avatar_id' => $params['aiAvatarId'] ?? '',
                'audio_source_from' => $params['audioSourceFrom'],
                'audio_file_id' => $params['audioFileId'] ?? '',
                'tts_text' => $params['ttsText'] ?? '',
                'voiceover_id' => $params['voiceoverId'] ?? '',
                'notice_url' => $params['noticeUrl'] ?? '',
                'status' => 0, // 待处理
                'points' => $points,
                'create_time' => time(),
                'update_time' => time()
            ];
            Db::name('video_avatar_tasks')->insert($taskData);
            
            Db::commit();
            return $result;
            
        } catch (\Exception $e) {
            Db::rollback();
            throw $e;
        }
    }

    /**
     * 计算需要扣除的积分
     */
    private function calculatePoints($params)
    {
        $config = config('points.points.video_avatar');
        return $config['points'];
    }

    /**
     * 查询任务状态并更新
     */
    public function queryTask(string $taskId)
    {
        \think\facade\Log::info("开始查询任务: {$taskId}");
        
        // 调用API查询任务
        \think\facade\Log::info("调用API查询任务: {$taskId}");
        $result = $this->apiService->queryVideoAvatarTask($taskId);
        \think\facade\Log::info("API查询完成: {$taskId}, 状态: " . ($result['status'] ?? 'unknown'));
        
        // 更新数据库状态
        $data = [
            'status' => $this->convertStatus($result['status']),
            'update_time' => time()
        ];
        
        // 记录消耗的积分
        if (isset($result['costCredit']) && is_numeric($result['costCredit'])) {
            $data['cost_credit'] = $result['costCredit'];
            \think\facade\Log::info("记录消耗积分: {$taskId}, 积分: {$result['costCredit']}");
        }
        
        // 如果生成成功，处理视频地址
        if ($result['status'] === 'success') {
            \think\facade\Log::info("任务成功，开始处理视频: {$taskId}");
            $originalVideoUrl = $result['outputVideoUrl'] ?? '';
            if ($originalVideoUrl) {
                \think\facade\Log::info("原始视频地址: {$taskId}, URL: {$originalVideoUrl}");
                try {
                    // 将视频上传到COS
                    \think\facade\Log::info("开始上传视频到COS: {$taskId}");
                    $cosVideoUrl = $this->uploadVideoToOss($originalVideoUrl, $taskId);
                    $data['video_url'] = $cosVideoUrl;
                    \think\facade\Log::info("视频上传COS成功: {$taskId}, COS地址: {$cosVideoUrl}");
                } catch (\Exception $e) {
                    // 如果COS上传失败，仍然保存原始URL
                    $data['video_url'] = $originalVideoUrl;
                    \think\facade\Log::error("Video upload to COS failed - TaskID: {$taskId}, Error: " . $e->getMessage());
                }
            } else {
                \think\facade\Log::warning("任务成功但无视频地址: {$taskId}");
            }
        }
        
        // 如果生成失败，保存错误信息并返还积分
        if ($result['status'] === 'fail') {
            $errorMsg = $result['errorMsg'] ?? '生成失败';
            $data['error_msg'] = $errorMsg;
            \think\facade\Log::error("任务失败: {$taskId}, 错误: {$errorMsg}");
            
            // 返还用户积分
            try {
                $task = Db::name('video_avatar_tasks')->where('task_id', $taskId)->find();
                if ($task && $task['points'] > 0) {
                    $pointsService = new \app\service\PointsService();
                    $pointsService->recharge(
                        $task['user_id'], 
                        $task['points'],
                        '',
                        "数字人任务{$taskId}失败退还积分"
                    );
                    \think\facade\Log::info("任务失败已退还积分: {$taskId}, 退还积分: {$task['points']}");
                }
            } catch (\Exception $e) {
                \think\facade\Log::error("任务失败积分退还失败: {$taskId}, 错误: " . $e->getMessage());
            }
        }

        \think\facade\Log::info("更新数据库: {$taskId}");
        Db::name('video_avatar_tasks')->where('task_id', $taskId)->update($data);
        \think\facade\Log::info("任务查询完成: {$taskId}");
        
        return $result;
    }

    /**
     * 将视频上传到COS
     */
    private function uploadVideoToOss($videoUrl, $taskId)
    {
        \think\facade\Log::info("开始处理视频上传: {$taskId}, 原始URL: {$videoUrl}");
        
        // 检查是否已经是COS地址
        if (strpos($videoUrl, 'myqcloud.com') !== false) {
            \think\facade\Log::info("已经是COS地址，直接返回: {$taskId}");
            return $videoUrl; // 已经是COS地址，直接返回
        }
        
        // 检查是否是AWS S3地址
        if (strpos($videoUrl, 's3.amazonaws.com') !== false) {
            \think\facade\Log::info("检测到AWS S3地址: {$taskId}");
        }

        // 创建临时目录
        $tempDir = runtime_path() . 'temp/video_upload';
        if (!is_dir($tempDir)) {
            mkdir($tempDir, 0755, true);
        }

        // 生成临时文件名
        $extension = pathinfo(parse_url($videoUrl, PHP_URL_PATH), PATHINFO_EXTENSION) ?: 'mp4';
        $tempFile = $tempDir . '/' . uniqid() . '.' . $extension;
        \think\facade\Log::info("临时文件路径: {$taskId}, 文件: {$tempFile}");

        // 下载视频文件（带重试机制）
        \think\facade\Log::info("开始下载视频: {$taskId}");
        \think\facade\Log::info("视频URL: {$videoUrl}");
        
        $videoData = $this->downloadVideoWithRetry($videoUrl, $taskId);
        
        if ($videoData === false) {
            \think\facade\Log::error("下载视频失败: {$taskId}");
            throw new \Exception("下载视频失败: {$videoUrl}");
        }

        \think\facade\Log::info("视频下载成功: {$taskId}, 大小: " . strlen($videoData) . " 字节");

        // 保存到临时文件
        if (file_put_contents($tempFile, $videoData) === false) {
            \think\facade\Log::error("保存临时视频文件失败: {$taskId}");
            throw new \Exception("保存临时视频文件失败");
        }
        \think\facade\Log::info("临时文件保存成功: {$taskId}");

        try {
            // 上传到COS
            \think\facade\Log::info("开始上传到COS: {$taskId}");
            $ossService = new \app\service\OssService();
            $ossPath = "videoAvatar/{$taskId}/output." . $extension;
            \think\facade\Log::info("COS路径: {$taskId}, 路径: {$ossPath}");
            $ossUrl = $ossService->putObject($ossPath, $tempFile);
            \think\facade\Log::info("COS上传成功: {$taskId}, URL: {$ossUrl}");

            // 删除临时文件
            unlink($tempFile);
            \think\facade\Log::info("临时文件删除成功: {$taskId}");

            return $ossUrl;

        } catch (\Exception $e) {
            \think\facade\Log::error("COS上传失败: {$taskId}, 错误: " . $e->getMessage());
            // 删除临时文件
            if (file_exists($tempFile)) {
                unlink($tempFile);
                \think\facade\Log::info("清理临时文件: {$taskId}");
            }
            throw $e;
        }
    }

    /**
     * 获取任务列表
     * @param int $userId 用户ID，如果提供则只获取该用户的任务
     * @param int $page 页码
     * @param int $pageSize 每页数量
     */
    public function getTaskList($userId = null, $page = 1, $pageSize = 10)
    {
        $query = Db::name('video_avatar_tasks')
            ->field([
                'id',
                'task_id',
                'user_id',
                'avatar_source_from',
                'video_file_id',
                'ai_avatar_id',
                'audio_source_from',
                'audio_file_id',
                'tts_text',
                'voiceover_id',
                'points',
                'cost_credit',
                'status',
                'video_url',
                'error_msg',
                'create_time',
                'update_time'
            ]);
        
        if ($userId !== null) {
            $query = $query->where('user_id', $userId);
        }
        
        return $query->order('id', 'desc')
            ->paginate([
                'list_rows' => $pageSize,
                'page' => $page,
                'var_page' => 'page'
            ])
            ->each(function($item) {
                // 格式化状态显示
                $item['status_text'] = $this->getStatusText($item['status']);
                // 格式化来源显示
                $item['avatar_source_text'] = $item['avatar_source_from'] == 0 ? '用户上传视频' : '公模列表';
                $item['audio_source_text'] = $item['audio_source_from'] == 0 ? '用户上传音频' : '文本转语音';
                // 格式化时间
                $item['create_time'] = date('Y-m-d H:i:s', $item['create_time']);
                $item['update_time'] = date('Y-m-d H:i:s', $item['update_time']);
                return $item;
            });
    }

    /**
     * 获取状态文本
     */
    private function getStatusText($status)
    {
        $statusMap = [
            0 => '待处理',
            1 => '处理中',
            2 => '已完成',
            3 => '失败'
        ];
        return $statusMap[$status] ?? '未知状态';
    }

    /**
     * 转换状态
     */
    private function convertStatus($status)
    {
        $statusMap = [
            'init' => 0,    // 待处理
            'running' => 1, // 处理中
            'success' => 2,  // 已完成
            'fail' => 3      // 失败
        ];
        return $statusMap[$status] ?? 0;
    }

    /**
     * 使用cURL下载视频文件，带重试机制
     * @param string $url 下载URL
     * @param string $taskId 任务ID（用于日志）
     * @param int $maxRetries 最大重试次数
     * @return string|false 文件内容或false
     */
    private function downloadVideoWithRetry($url, $taskId, $maxRetries = 3)
    {
        for ($attempt = 1; $attempt <= $maxRetries; $attempt++) {
            try {
                \think\facade\Log::info("下载视频尝试 {$attempt}/{$maxRetries}: {$taskId}");
                
                $ch = curl_init();
                curl_setopt_array($ch, [
                    CURLOPT_URL => $url,
                    CURLOPT_RETURNTRANSFER => true,
                    CURLOPT_FOLLOWLOCATION => true,
                    CURLOPT_MAXREDIRS => 10,
                    CURLOPT_TIMEOUT => 300, // 5分钟超时，视频文件可能较大
                    CURLOPT_CONNECTTIMEOUT => 30,
                    CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
                    CURLOPT_SSL_VERIFYPEER => false,
                    CURLOPT_SSL_VERIFYHOST => false,
                    CURLOPT_HEADER => false,
                    CURLOPT_HTTPHEADER => [
                        'Accept: */*',
                        'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8',
                        'Accept-Encoding: gzip, deflate, br',
                        'Connection: keep-alive',
                        'Upgrade-Insecure-Requests: 1'
                    ],
                    CURLOPT_ENCODING => '', // 自动解压
                ]);

                // 检查是否有代理设置
                $proxy = getenv('https_proxy') ?: getenv('http_proxy');
                if ($proxy) {
                    curl_setopt($ch, CURLOPT_PROXY, $proxy);
                    curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
                    \think\facade\Log::info("使用代理: {$proxy}");
                }

                $content = curl_exec($ch);
                $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
                $error = curl_error($ch);
                $contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
                $contentLength = curl_getinfo($ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD);
                curl_close($ch);

                \think\facade\Log::info("下载结果: {$taskId}, HTTP: {$httpCode}, Content-Type: {$contentType}, Content-Length: {$contentLength}, 错误: {$error}");

                if ($content === false || !empty($error)) {
                    throw new \Exception("cURL错误: " . $error);
                }

                if (in_array($httpCode, [200, 206])) {
                    \think\facade\Log::info("视频下载成功: {$taskId}, 大小: " . strlen($content) . " 字节");
                    return $content;
                } else {
                    throw new \Exception("HTTP错误: " . $httpCode);
                }

            } catch (\Exception $e) {
                \think\facade\Log::error("下载视频失败 (尝试 {$attempt}/{$maxRetries}): {$taskId}, 错误: " . $e->getMessage());
                
                if ($attempt === $maxRetries) {
                    \think\facade\Log::error('下载视频最终失败', [
                        'taskId' => $taskId,
                        'url' => $url,
                        'attempt' => $attempt,
                        'error' => $e->getMessage()
                    ]);
                    return false;
                }
                
                // 指数退避重试
                // $delay = pow(2, $attempt - 1);
                // \think\facade\Log::info("等待 {$delay} 秒后重试: {$taskId}");
                // sleep($delay);
            }
        }
        
        return false;
    }
} 